﻿// Copyright (c) 2014 Morten Bakkedal
// This code is published under the MIT License.

using System;
using System.Collections.Generic;
using System.Diagnostics;

using FuncLib.DualFunctions.Compilation;

namespace FuncLib.DualFunctions
{
	/// <summary>
	/// Represents a variable carrying dual (or augmented) information in form of the derivative structure. When using instances
	/// of this class to build functions, derivatives are computed in recursion-free forward-mode. If combined with any function
	/// depending on ordinary <see cref="Variable" /> instances, computations from that point on is performed using the usual
	/// recursive implementation. Supports up to second order partial derivatives. The order may be specified independently for
	/// each variable.
	/// </summary>
	[Serializable]
	//[DebuggerStepThrough]
	[DebuggerDisplay("{DebuggerDisplay,nq}")]
	public partial class DualVariable : DualFunction
	{
		/// <summary>
		/// Creates a new variable with derivatives computed up to second order.
		/// </summary>
		public DualVariable()
			: this(null)
		{
		}

		/// <summary>
		/// Creates a new variable with derivatives computed up to the order specified.
		/// </summary>
		public DualVariable(int order)
			: this(null, order)
		{
		}

		/// <summary>
		/// Creates a new named variable with derivatives computed up to second order.
		/// </summary>
		public DualVariable(string name)
			: this(name, 2)
		{
		}

		/// <summary>
		/// Creates a new named variable with derivatives computed up to the order specified.
		/// </summary>
		public DualVariable(string name, int order)
		{
			if (order < 0)
			{
				throw new ArgumentOutOfRangeException("order");
			}

			if (order > 2)
			{
				throw new NotSupportedException("Computing derivatives higher than second order isn’t supported using this method.");
			}

			Order = order;
			InnerVariable = new DualInnerVariable(name, this);
			TestFunction = DualFunction.Disable(InnerVariable);
		}

		internal override GeneratedFunction Compile(DualCodeGenerator generator)
		{
			return generator.AddVariable(this);
		}

		internal override IEnumerable<DualFunction> GetDependencies()
		{
			yield break;
		}

		/// <summary>
		/// Determines whether first order partial derivatives are computed with respect to this variable.
		/// </summary>
		public static bool CheckFirstOrder(DualVariable variable)
		{
			return variable.Order >= 1;
		}

		/// <summary>
		/// Determines whether second order partial derivatives are computed with respect to these variables.
		/// </summary>
		public static bool CheckSecondOrder(DualVariable variable1, DualVariable variable2)
		{
			// At least one of the variables must have order 2 and both variables must have order 1 (or more).
			return (variable1.Order >= 2 || variable2.Order >= 2) && variable1.Order >= 1 && variable2.Order >= 1;
		}

		/// <summary>
		/// The order up to which derivatives are computed with respect to this variable. Use <see cref="CheckSecondOrder" />
		/// to determine whether derivatives with respect to a mixed set of variables are computed.
		/// </summary>
		public int Order
		{
			get;
			private set;
		}

		/// <summary>
		/// The <see cref="Variable" /> representation of this <see cref="DualVariable" />.
		/// </summary>
		public DualInnerVariable InnerVariable
		{
			get;
			private set;
		}

		/// <summary>
		/// The name of the variable.
		/// </summary>
		public string Name
		{
			get
			{
				return InnerVariable.Name;
			}
		}

		private string DebuggerDisplay
		{
			get
			{
				// Just like in Variable class.
				return Name ?? "{" + GetType().FullName + "}";
			}
		}
	}
}
